/** @file    ModemDslBundle.cpp
 *  @author  Alessandro Polo
 *  @version $Id: ModemDslBundle.cpp 2884 2010-08-10 00:31:45Z alex $
 *  @brief
 * File containing methods for the wosh::devices::ModemDslBundle class.
 * The header for this class can be found in ModemDslBundle.h, check that file
 * for class description.
 ****************************************************************************/
/* Copyright (c) 2007-2010, WOSH - Wide Open Smart Home 
 * by Alessandro Polo - OpenSmartHome.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSmartHome.com WOSH nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Alessandro Polo ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Alessandro Polo BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ****************************************************************************/

 #include "ModemDslBundle.h"
 #include "ModemDslImpl.h"

 #include <core/ObjectAllocator.h>
 #include <core/MethodsCommon.h>
 #include <core/Fact.h>
 #include <core/ThreadManager.h>
 #include <core/DeviceManager.h>
 #include <core/SystemError.h>
 #include <core/WoshKernel.h>

 #include <core/MessageFilter.h>
 #include <core/MessageProcessingCommon.h>
 #include <stdio.h>


namespace wosh {
 namespace devices {

 WOSH_REGISTER(wosh::devices::ModemDslBundle, "wosh::interfaces::devices::ModemDsl", _ModemDsl_VERSION, _static_ModemDslBundle1 )
 WOSH_REGISTER(wosh::devices::ModemDslBundle, "wosh::BundleGeneric", _ModemDsl_VERSION, _static_ModemDslBundle2 )

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS

ModemDslBundle::ModemDslBundle( const std::string& name )
	: BundleGeneric() {
	if ( name.size() > 0 )	BundleGeneric::setName( name, false );
	else					BundleGeneric::setName( _ModemDsl_NAME, false );
	Properties.update( _KEY_Version, _ModemDsl_VERSION );

	Log(LOG_DEBUG, " Configuring Modem worker.." );
	this->modemWorker = new ModemDslImpl(*this);
	this->modemWorker->setRefreshFrequency(1);
	this->modemWorker->setControlScript( _ModemDsl_DEFAULT_Script );
	this->modemWorker->setPingCheckAddress( _ModemDsl_DEFAULT_PingCheckAddress );
	this->modemWorker->setPppInterface(_ModemDsl_DEFAULT_pppInterface );
	this->modemWorker->setModemListener(this);

	this->connState = wosh::interfaces::devices::Modem::STATE_UNKNOWN;
	this->devState = wosh::interfaces::Device::UNKNOWN;

	Log(LOG_DEBUG, " Setting default properties and permissions.." );
	Properties.set( _ModemDsl_KEY_RefreshFrequency,	1,									Permission::RW_RW_R__() );
	Properties.set( _ModemDsl_KEY_PingCheckAddress,	_ModemDsl_DEFAULT_PingCheckAddress,	Permission::RW_RW_R__() );
	Properties.set( _ModemDsl_KEY_CtrlScript,		_ModemDsl_DEFAULT_Script,			Permission::RW_RW_R__() );
	Properties.set( _ModemDsl_KEY_pppInterface,		_ModemDsl_DEFAULT_pppInterface,		Permission::RW_RW_R__() );

	Properties.set( _Modem_KEY_ConnectionStatus,	wosh::interfaces::devices::Modem::getConnectionStateAsString(wosh::interfaces::devices::Modem::STATE_UNKNOWN), Permission( Permission::Read ) );
	Properties.set( _Device_KEY_DeviceStatus,		wosh::interfaces::Device::getDeviceStateAsString(wosh::interfaces::Device::UNKNOWN), Permission( Permission::Read ) );

	Properties.set( _Modem_KEY_IpAddress,			"", Permission( Permission::Read ) );
	Properties.set( _Modem_KEY_ConnectedTime,	"", Permission( Permission::Read ) );
	Properties.set( _Modem_KEY_statsSpeed,		"", Permission( Permission::Read ) );
	Properties.set( _Modem_KEY_statsSpeedTop,	"", Permission( Permission::Read ) );
	Properties.set( _Modem_KEY_statsPacketsTx,	"", Permission( Permission::Read ) );
	Properties.set( _Modem_KEY_statsPacketsRx,	"",	Permission( Permission::Read ) );
	Properties.set( _Modem_KEY_statsBytesTx,		"", Permission( Permission::Read ) );
	Properties.set( _Modem_KEY_statsBytesRx,		"", Permission( Permission::Read ) );
	Properties.set( _Modem_KEY_statsErrorsTx,	"", Permission( Permission::Read ) );
	Properties.set( _Modem_KEY_statsErrorsRx,	"", Permission( Permission::Read ) );
	Properties.monitor(_ModemDsl_KEY_RefreshFrequency);
	Properties.monitor(_ModemDsl_KEY_CtrlScript);
	Properties.monitor(_ModemDsl_KEY_PingCheckAddress);
	Properties.monitor(_ModemDsl_KEY_pppInterface);

	Log(LOG_DEBUG, " Registering methods.." );
	MethodRequest* mmConnect = Methods.create<MethodRequest>( _Modem_METHOD_connect );
	mmConnect->setMethod( this, (MethodRequestPtr)&ModemDslBundle::mmDoConnect );
	mmConnect->getPermission().setMask( Permission::RX, Permission::RX, Permission::RX );

	MethodWRESULT* mmDisconnect = Methods.create<MethodWRESULT>( _Modem_METHOD_disconnect );
	mmDisconnect->setMethod( this, (MethodWRESULTPtr)&ModemDslBundle::disconnect ); //direct method
	mmDisconnect->getPermission().setMask( Permission::RX, Permission::RX, Permission::RX );

	MethodWRESULT* mmReconnect = Methods.create<MethodWRESULT>( _Modem_METHOD_reconnect );
	mmReconnect->setMethod( this, (MethodWRESULTPtr)&ModemDslBundle::reconnect ); //direct method
	mmReconnect->getPermission().setMask( Permission::RX, Permission::RX, Permission::RX );

	MethodRequest* mmCheckConnected = Methods.create<MethodRequest>( _Modem_METHOD_checkConnected );
	mmCheckConnected->setMethod( this, (MethodRequestPtr)&ModemDslBundle::mmDoCheckConnected );
	mmCheckConnected->getPermission().setMask( Permission::RX, Permission::RX, Permission::RX );

	MethodWRESULT* mmResetStats = Methods.create<MethodWRESULT>( _Modem_METHOD_resetStats );
	mmResetStats->setMethod( this, (MethodWRESULTPtr)&ModemDslBundle::resetStats ); //direct method
	mmResetStats->getPermission().setMask( Permission::RX, Permission::RX, Permission::RX );

	// method for debug purpose
	MethodRequest* mmForceState = Methods.create<MethodRequest>( _ModemDsl_METHOD_forceState );
	mmForceState->setMethod( this, (MethodRequestPtr)&ModemDslBundle::mmDoForceState );
	mmForceState->getPermission().setOwner( "root" );
	mmForceState->getPermission().setGroup( "root" );
	mmForceState->getPermission().setMask( Permission::RX, Permission::Read, Permission::None );

	setBundleState(Bundle::STATE_CREATED, false);
 }

ModemDslBundle::~ModemDslBundle() {
	Log(LOG_INFO, " Destroying.." );
	if ( isBundleRunning() ) {
		Log(LOG_WARNING, "~ModemDslBundle() : Destroying while Bundle is running! Trying to stop.." );
		bundleStop();
	 }
	if ( this->modemWorker->isThreadRunning() ) {
		Log(LOG_WARNING, "~ModemDslBundle() : Destroying while RUNNING! Trying to stop.." );
		this->modemWorker->quitThread();
		this->modemWorker->waitThread(30000);
	 }
	delete this->modemWorker; this->modemWorker = NULL;
	Log(LOG_VERBOSE, ":~ModemDslBundle() : Destroyed." );
 }

//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////// BUNDLE CONTROL

WRESULT ModemDslBundle::bundleStart() {
	if ( !BundleGeneric::bundleValidate_StartStop(Bundle::STATE_STARTING) ) return WRET_ERR_WRONG_STATE;

	BusDevices.setMessageHandler(this);
	WRESULT bus_connected = BusDevices.connect( _Bus_Devices, Bus::ACTION_DEFERRED );
	if ( WFAILED(bus_connected) )
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "ModemDslBundle", "FAILED#%d Connecting Bus ("_Bus_Devices")..", bus_connected );

	WRESULT ret = BundleGeneric::start_SynchThread( this->modemWorker );
	if ( WSUCCEEDED(ret) ) {
		Log(LOG_VERBOSE, ":bundleStart() : Registering as Device.." );
		wosh::Device* myself = new wosh::Device(*this);
		myself->getInterfaces() = this->Interfaces;
		WoshKernel::getInstance()->devices().registerDevice( myself );
	 }
	// BUNDLE-STATE (STARTED) will be updated async by WORKER, through call: IThreadListener::thread_event()
	return ret;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT ModemDslBundle::bundleStop() {
	if ( !BundleGeneric::bundleValidate_StartStop(Bundle::STATE_STOPPING) ) return WRET_ERR_WRONG_STATE;

	WRESULT ret = BundleGeneric::stop_SynchThread( this->modemWorker );
	if ( WSUCCEEDED(ret) ) {
		Log(LOG_VERBOSE, ":bundleStop() : UnRegistering as Device.." );
		WoshKernel::getInstance()->devices().unRegisterDevice( this->getName() );
	 }
	// BUNDLE-STATE (STOPPED) will be updated async by WORKER, through call: IThreadListener::thread_event()
	return ret;
 }

WRESULT ModemDslBundle::bundleDiagnostic() {
	Log(LOG_VERBOSE, ":bundleDiagnostic() : Starting ModemDslBundle-Diagnostic" );

	WRESULT ret = BundleGeneric::bundleDiagnostic();

	WRESULT retC = checkConnected();
	if ( WFAILED(retC) )
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "bundleDiagnostic", "FAILED#%d Check-Connected..", retC );
	ret += retC;

	Log(LOG_INFO, ":bundleDiagnostic() : Finished ModemDslBundle-Diagnostic [%d]", ret );
	return ret;
 }

////////////////////////////////////////////////////////////////////////////////////////////// BUNDLE CONTROL
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////// PROCESS BUS MESSAGES

void ModemDslBundle::busMessage( const Message& message, const Bus* source ) {
	DeviceManager::busMessageForBundleDevices( message, source, this, this->Methods, this->Log, this->BusCore, this->BusDevices );
 }

//////////////////////////////////////////////////////////////////////////////////////// PROCESS BUS MESSAGES
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////// PROPERTY EVENTS

bool ModemDslBundle::updatingProperty( bool& do_update, const Variant& value_proposed, Property& property_current, const PropertiesProvider* source ) {
	if ( property_current.getKey() == _ModemDsl_KEY_RefreshFrequency ) {
		this->modemWorker->setRefreshFrequency( value_proposed.toInteger(-1) );
		return true;
	 }
	else if ( property_current.getKey() == _ModemDsl_KEY_CtrlScript ) {
		this->modemWorker->setControlScript( value_proposed.toString() );
		return true;
	 }
	else if ( property_current.getKey() == _ModemDsl_KEY_PingCheckAddress ) {
		this->modemWorker->setPingCheckAddress( value_proposed.toString() );
		return true;
	 }
	else if ( property_current.getKey() == _ModemDsl_KEY_pppInterface ) {
		this->modemWorker->setPppInterface( value_proposed.toString() );
		return true;
	 }
	else // forward processing to base class
		return BundleGeneric::updatingProperty(do_update, value_proposed, property_current, source);
 }

///////////////////////////////////////////////////////////////////////////////////////////// PROPERTY EVENTS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////// GENERATED EVENTS

void ModemDslBundle::setConnectionState( wosh::interfaces::devices::Modem::CONNECTION_STATE state, int doRaiseEvent ) {
	wosh::interfaces::devices::Modem::CONNECTION_STATE oldState = this->connState;
	this->connState = state;
	const char* stateStr = wosh::interfaces::devices::Modem::getConnectionStateAsString(state);

	if ( state == oldState ) {
		if ( Log.getLevel() == LOG_VERBOSE )
			Log(LOG_VERBOSE, ":setConnectionState(%s)", stateStr );
		if ( doRaiseEvent != 2 )
			return;
	 }
	else { // connection state changed
		Log(LOG_INFO, ":setConnectionState(%s)", stateStr );

		if ( state == wosh::interfaces::devices::Modem::STATE_UNKNOWN )
			this->devState = wosh::interfaces::Device::UNKNOWN;
		else
			this->devState = wosh::interfaces::Device::LINKED;

		Properties.transactionBeginWrite();
		Properties.update(_Modem_KEY_ConnectionStatus, stateStr );
		Properties.update(_Device_KEY_DeviceStatus, wosh::interfaces::Device::getDeviceStateAsString() );
		Properties.transactionEnd();
	 }
	if ( doRaiseEvent == 0 ) return;
	Message* msg_event = new Message( new Modem_ConnectionChanged(state, oldState) );
	msg_event->getSource().setKernelLocal();
	msg_event->getSource().setPath("Devices"); // overwrite source (as device)
	msg_event->getSource().setName( this->getName() );
	msg_event->setDestinationBroadcast(true);
	if ( bundleCredentials() == NULL )
		SecurityManager::getCredentialImplicit().signMessage(msg_event, this);
	else
		bundleCredentials()->signMessage(msg_event, this);
	BusDevices.postMessage(msg_event);
 }

//////////////////////////////////////////////////////////////////////////////////////////// GENERATED EVENTS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////// THREAD EVENTS

void ModemDslBundle::thread_event( Thread::THREAD_STATE thread_event, Thread* thread_source ) {
	long id = 0;
	if ( thread_source != NULL ) id = thread_source->getThreadID();
	Log(LOG_VERBOSE, ":thread_event(%ld) : %s", id, Thread::getThreadStateAsString(thread_event) );
	switch(thread_event) {
		case Thread::STATE_RUNNING: {
				setBundleState( Bundle::STATE_STARTED );
				Properties.transactionBeginWrite();
				Properties.update( _Device_KEY_DeviceStatus, wosh::interfaces::Device::getDeviceStateAsString(wosh::interfaces::Device::LINKED) );
				Properties.transactionEnd();
				break;
			 }
		case Thread::STATE_STOPPED: {
				setBundleState( Bundle::STATE_STOPPED );
				Properties.transactionBeginWrite();
				Properties.update( _Device_KEY_DeviceStatus, wosh::interfaces::Device::getDeviceStateAsString(wosh::interfaces::Device::UNLINKED) );
				Properties.transactionEnd();
				break;
			 }
		default: break;
	 }
 }
/////////////////////////////////////////////////////////////////////////////////////////////// THREAD EVENTS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////// MODEM EVENTS

void ModemDslBundle::modem_ConnectionState( ModemDslImpl*, wosh::interfaces::devices::Modem::CONNECTION_STATE state ) {
	Log(LOG_VERBOSE, ":modem_ConnectionState(%d)", (int)state );
	setConnectionState(state);
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT ModemDslBundle::connect( std::string params )	{ return this->modemWorker->connect(params); }
WRESULT ModemDslBundle::disconnect()					{ return this->modemWorker->disconnect(); }
WRESULT ModemDslBundle::reconnect()						{ return this->modemWorker->reconnect(); }
WRESULT ModemDslBundle::checkConnected()				{ return this->modemWorker->checkConnected(); }

WRESULT ModemDslBundle::resetStats()					{ return this->modemWorker->resetStats(); }
long ModemDslBundle::getRefreshFrequency() const		{ return this->modemWorker->getRefreshFrequency(); }
std::string ModemDslBundle::getPingCheckAddress() const	{ return this->modemWorker->getPingCheckAddress(); }
std::string ModemDslBundle::getCtrlScript() const		{ return this->modemWorker->getControlScript(); }
std::string ModemDslBundle::getPppInterface() const		{ return this->modemWorker->getPppInterface(); }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

void ModemDslBundle::modem_StatsUpdate( ModemDslImpl*, const pppStats& stats ) {
	Log(LOG_VERBOSE, ":modem_StatsUpdate(%d)", stats.online );

	if ( stats.online == 0 )		setConnectionState(wosh::interfaces::devices::Modem::STATE_DISCONNECTED);
	else if ( stats.online == 1 )	setConnectionState(wosh::interfaces::devices::Modem::STATE_CONNECTED);
	else							setConnectionState(wosh::interfaces::devices::Modem::STATE_UNKNOWN);

	char buffer[255];
	memset(buffer, '\0', 255);

	Properties.transactionBeginWrite();

	sprintf(buffer, "%s", stats.ip_addr_rtrn);
	Properties.update( _Modem_KEY_IpAddress, std::string(buffer) );

	sprintf(buffer, "%.0f", stats.tx_bytes);
	Properties.update( _Modem_KEY_statsBytesTx, std::string(buffer) );

	sprintf(buffer, "%.0f", stats.rx_bytes);
	Properties.update( _Modem_KEY_statsBytesRx, std::string(buffer) );

	sprintf(buffer, "%ld", stats.tx_packets);
	Properties.update( _Modem_KEY_statsPacketsTx, std::string(buffer) );

	sprintf(buffer, "%ld", stats.rx_packets);
	Properties.update( _Modem_KEY_statsPacketsRx, std::string(buffer) );

	memset(buffer, '\0', 255);
	sprintf(buffer, "%ld", stats.tx_errors);
	Properties.update( _Modem_KEY_statsErrorsTx, std::string(buffer) );

	memset(buffer, '\0', 255);
	sprintf(buffer, "%ld", stats.rx_errors);
	Properties.update( _Modem_KEY_statsErrorsRx, std::string(buffer) );

	memset(buffer, '\0', 255);
	sprintf(buffer, "%ld days, %d:%02d:%02d", stats.online_days, stats.online_hour, stats.online_min, stats.online_sec );
	Properties.update( _Modem_KEY_ConnectedTime, std::string(buffer) );

	memset(buffer, '\0', 255);
	if( stats.curr_speed >= 1 ) 
		sprintf(buffer, "%.2f KB/s", stats.curr_speed);
	else
		sprintf(buffer, "%.0f Bytes/s", stats.curr_speed*1024);
	Properties.update( _Modem_KEY_statsSpeed, std::string(buffer) );

	memset(buffer, '\0', 255);
	sprintf(buffer, "%.2f KB/s", stats.top_speed);
	Properties.update( _Modem_KEY_statsSpeedTop, std::string(buffer) );
 
	Properties.transactionEnd();
 }

//////////////////////////////////////////////////////////////////////////////////////////////// MODEM EVENTS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////// METHODS

Response* ModemDslBundle::mmDoConnect( const Request* request ) {
	if ( request == NULL ) return NULL;
	std::string params = request->getArgument().toString();
	Log(LOG_VERBOSE, ":mmDoConnect(%s)", params.c_str() );
	WRESULT ret = connect( params );
	// answer the request, also decorate response with current state (should be same just set)
	return request->replyResponse(ret, wosh::interfaces::devices::Modem::getConnectionStateAsString() );
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

Response* ModemDslBundle::mmDoCheckConnected( const Request* request ) {
	if ( request == NULL ) return NULL;
	Log(LOG_VERBOSE, ":mmDoCheckConnected()" );
	WRESULT ret = checkConnected();
	// answer the request, also decorate response with current state (should be same just set)
	return request->replyResponse(ret, wosh::interfaces::devices::Modem::getConnectionStateAsString() );
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

Response* ModemDslBundle::mmDoForceState( const Request* request ) {
	if ( request == NULL ) return NULL;
	std::string stateStr = request->getArgument().toString();
	Log(LOG_WARNING, ":mmForceState(%s)", stateStr.c_str() );
	// overwrite connection state
	wosh::interfaces::devices::Modem::CONNECTION_STATE state = wosh::interfaces::devices::Modem::getConnectionStateFromString( stateStr.c_str() );
	setConnectionState( state );
	// answer the request, also decorate response with current state (should be same just set)
	return request->replyResponse(WRET_OK, getConnectionStateAsString());
 }

///////////////////////////////////////////////////////////////////////////////////////////////////// METHODS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

 }; // namespace devices
}; // namespace wosh
